package com.rainsoft.center.isec.common.utils;

import com.alibaba.fastjson.annotation.JSONField;
import com.rainsoft.center.isec.common.entity.Constants;
import scala.Tuple2;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * JDBC辅助组件
 * <p>
 * 在正式的项目的代码编写过程中，是完全严格按照大公司的coding标准来的
 * 也就是说，在代码中，是不能出现任何hard code（硬编码）的字符
 * 比如“张三”、“com.mysql.jdbc.Driver”
 * 所有这些东西，都需要通过常量来封装和使用
 *
 * @author Administrator
 */
public class JDBCHelper {
	static {
		try {
			String driver = Constants.JDBC_DRIVER;
			Class.forName(driver);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 第二步，实现JDBCHelper的单例化
	// 为什么要实现代理化呢？因为它的内部要封装一个简单的内部的数据库连接池
	// 为了保证数据库连接池有且仅有一份，所以就通过单例的方式
	// 保证JDBCHelper只有一个实例，实例中只有一份数据库连接池
	private static JDBCHelper instance = null;

	/**
	 * 获取单例
	 *
	 * @return 单例
	 */
	public static JDBCHelper getInstance() {
		if (instance == null) {
			synchronized (JDBCHelper.class) {
				if (instance == null) {
					instance = new JDBCHelper();
				}
			}
		}
		return instance;
	}

	// 数据库连接池
	private LinkedList<Connection> datasource = new LinkedList<Connection>();

	/**
	 * 第三步：实现单例的过程中，创建唯一的数据库连接池
	 * <p>
	 * 私有化构造方法
	 * <p>
	 * JDBCHelper在整个程序运行声明周期中，只会创建一次实例
	 * 在这一次创建实例的过程中，就会调用JDBCHelper()构造方法
	 * 此时，就可以在构造方法中，去创建自己唯一的一个数据库连接池
	 */
	private JDBCHelper() {
		// 首先第一步，获取数据库连接池的大小，就是说，数据库连接池中要放多少个数据库连接
		// 这个，可以通过在配置文件中配置的方式，来灵活的设定
		int datasourceSize =
				Constants.JDBC_DATASOURCE_SIZE;

		// 然后创建指定数量的数据库连接，并放入数据库连接池中
		for (int i = 0; i < datasourceSize; i++) {
			String url = null;
			String user = null;
			String password = null;

			url = Constants.JDBC_URL;
			user = Constants.JDBC_USERNAME;
			password = Constants.JDBC_PASSWORD;


			try {
				Connection conn = DriverManager.getConnection(url, user, password);
				datasource.push(conn);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 第四步，提供获取数据库连接的方法
	 * 有可能，你去获取的时候，这个时候，连接都被用光了，你暂时获取不到数据库连接
	 * 所以我们要自己编码实现一个简单的等待机制，去等待获取到数据库连接
	 */
	public synchronized Connection getConnection() {
		while (datasource.size() == 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return datasource.poll();
	}

	/**
	 * 第五步：开发增删改查的方法
	 * 1、执行增删改SQL语句的方法
	 * 2、执行查询SQL语句的方法
	 * 3、批量执行SQL语句的方法
	 */

	/**
	 * 执行增删改SQL语句
	 *
	 * @param sql
	 * @param params
	 * @return 影响的行数
	 */
	public int executeUpdate(String sql, Object[] params) {
		int rtn = 0;
		Connection conn = null;
		PreparedStatement pstmt = null;

		try {
			conn = getConnection();
			conn.setAutoCommit(false);

			pstmt = conn.prepareStatement(sql);

			if (params != null && params.length > 0) {
				for (int i = 0; i < params.length; i++) {
					pstmt.setObject(i + 1, params[i]);
				}
			}

			rtn = pstmt.executeUpdate();

			conn.commit();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				datasource.push(conn);
			}
		}

		return rtn;
	}

	/**
	 * 执行查询SQL语句
	 *
	 * @param sql
	 * @param params
	 * @param callback
	 */
	public void executeQuery(String sql, Object[] params,
	                         QueryCallback callback) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;

		try {
			conn = getConnection();
			pstmt = conn.prepareStatement(sql);

			if (params != null && params.length > 0) {
				for (int i = 0; i < params.length; i++) {
					pstmt.setObject(i + 1, params[i]);
				}
			}

			rs = pstmt.executeQuery();

			callback.process(rs);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				datasource.push(conn);
			}
		}
	}


	/**
	 * 执行查询SQL语句
	 *
	 * @param sql
	 * @param params
	 */
	public ResultSet executeQuery(String sql, Object[] params) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;

		try {
			conn = getConnection();
			pstmt = conn.prepareStatement(sql);

			if (params != null && params.length > 0) {
				for (int i = 0; i < params.length; i++) {
					pstmt.setObject(i + 1, params[i]);
				}
			}

			return pstmt.executeQuery();

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				datasource.push(conn);
			}
		}
		return null;
	}


	/**
	 * @param sql
	 * @param paramsList
	 * @return 每条SQL语句影响的行数
	 */
	public int[] executeBatch(String sql, List<Object[]> paramsList) {
		int[] rtn = null;
		Connection conn = null;
		PreparedStatement pstmt = null;

		try {
			conn = getConnection();

			// 第一步：使用Connection对象，取消自动提交
			conn.setAutoCommit(false);

			pstmt = conn.prepareStatement(sql);

			// 第二步：使用PreparedStatement.addBatch()方法加入批量的SQL参数
			if (paramsList != null && paramsList.size() > 0) {
				for (Object[] params : paramsList) {
					for (int i = 0; i < params.length; i++) {
						pstmt.setObject(i + 1, params[i]);
					}
					pstmt.addBatch();
				}
			}

			// 第三步：使用PreparedStatement.executeBatch()方法，执行批量的SQL语句
			rtn = pstmt.executeBatch();

			// 最后一步：使用Connection对象，提交批量的SQL语句
			conn.commit();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				datasource.push(conn);
			}
		}

		return rtn;
	}

	/**
	 * 批量写入oracle 多个statement一起提交 可以控制事务
	 *
	 * @param paramsList Tuple2<sql,arsList>
	 * @return 每个sql对应的影响行数
	 */
	public List<int[]> executeBatch(List<Tuple2<String, List<Object[]>>> paramsList) {
		List<int[]> list = new ArrayList<>();
		Connection conn = null;
		try {
			conn = getConnection();
			// 第一步：使用Connection对象，取消自动提交
			conn.setAutoCommit(false);
			for (Tuple2<String, List<Object[]>> stringListTuple2 : paramsList) {
				PreparedStatement pstmt = conn.prepareStatement(stringListTuple2._1);
				// 第二步：使用PreparedStatement.addBatch()方法加入批量的SQL参数
				if (stringListTuple2._2.size() > 0) {
					for (Object[] params : stringListTuple2._2) {
						for (int i = 0; i < params.length; i++) {
							pstmt.setObject(i + 1, params[i]);
						}
						pstmt.addBatch();
						list.add(pstmt.executeBatch());
					}
				}
			}
			// 第三步：使用PreparedStatement.executeBatch()方法，执行批量的SQL语句

			// 最后一步：使用Connection对象，提交批量的SQL语句
			conn.commit();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				datasource.push(conn);
			}
		}

		return list;
	}


	/**
	 * 静态内部类：查询回调接口
	 *
	 * @author Administrator
	 */
	public static interface QueryCallback {

		/**
		 * 处理查询结果
		 *
		 * @param rs
		 * @throws Exception
		 */
		void process(ResultSet rs) throws Exception;

	}

	public <T> List<T> queryForList(String sql, Object[] params, Class<T> clazz) {
		ResultSet resultSet = executeQuery(sql, params);
		try {
			return genList(resultSet, clazz);
		} catch (SQLException | IllegalAccessException | InstantiationException e) {
			e.printStackTrace();
		}
		return null;
	}

	/*
   * 将rs结果转换成对象列表
   * @param rs jdbc结果集
   * @param clazz 对象的映射类
   * return 封装了对象的结果列表
   */
	public <T> List<T> genList(ResultSet rs, Class<T> clazz) throws SQLException, InstantiationException, IllegalAccessException {
		//结果集的元素对象
		ResultSetMetaData rsmd = rs.getMetaData();
		//获取结果集的元素个数
		int colCount = rsmd.getColumnCount();

		//返回结果的列表集合
		List<T> list = new ArrayList<>();
		//业务对象的属性数组
		Field[] fields = clazz.getDeclaredFields();
		while (rs.next()) {//对每一条记录进行操作
			Object obj = clazz.newInstance();//构造业务对象实体

			//将每一个字段取出进行赋值
			for (int i = 1; i <= colCount; i++) {
				Object value = rs.getObject(i);
				if (value instanceof BigDecimal) {
					value = ((BigDecimal) value).longValue();
				}
				//寻找该列对应的对象属性
				for (int j = 0; j < fields.length; j++) {
					Field f = fields[j];
					JSONField annotation = f.getAnnotation(JSONField.class);
					String fName = f.getName();
					if (annotation != null) {
						String name1 = annotation.name();
						if (!"".equals(name1)) {
							fName = name1;
						}
					}
					//如果匹配进行赋值
					if (fName.equalsIgnoreCase(rsmd.getColumnName(i))) {
						boolean flag = f.isAccessible();
						f.setAccessible(true);
						f.set(obj, value);
						f.setAccessible(flag);
					}
				}
			}
			list.add((T) obj);
		}
		return list;
	}


}
